﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interception.Components;
using MugenInjection.Interception.Interface;
using MugenInjection.Interception.Interface.Components;
using MugenInjection.Interface;
using MugenInjection.Parameters;

// ReSharper disable CheckNamespace
namespace MugenInjection.Activators
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// Represents the activator that use proxy to activate object.
    /// </summary>
    internal class InterceptorActivator : IActivator
    {
        #region Fields

        private readonly IActivator _nestedActivator;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="InterceptorActivator"/> class.
        /// </summary>
        public InterceptorActivator(IActivator nestedActivator)
        {
            _nestedActivator = nestedActivator;
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            _nestedActivator.Dispose();
        }

        #endregion

        #region Implementation of IDisposableObject

        /// <summary>
        /// Gets a value indicating whether this instance is disposed.
        /// </summary>
        public bool IsDisposed
        {
            get { return _nestedActivator.IsDisposed; }
        }

        public event Action<IDisposableObject> Disposed
        {
            add { _nestedActivator.Disposed += value; }
            remove { _nestedActivator.Disposed -= value; }
        }

        #endregion

        #region Implementation of IActivator

        /// <summary>
        /// Creates new instance of the specified service, and injects values into properties, fields, methods.
        /// </summary>
        /// <param name="constructorInfo">The specified <see cref="ConstructorInfo"/>.</param>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/>.</param>
        /// <returns>An instance of the service.</returns>
        public object Activate(ConstructorInfo constructorInfo, IBindingContext bindingContext)
        {
            var managerComponent = bindingContext.Injector.Components.Get<IInterceptionManagerComponent>();
            ISettings settings = bindingContext.Binding.Settings;
            Func<IProxyCollection> proxyCollectionFunc;
            if (!settings.TryGet(InterceptionManagerComponent.ProxyCollectionFuncKey, out proxyCollectionFunc) ||
                proxyCollectionFunc == null)
                throw new NotSupportedException("The InterceptorActivator can activate only the proxy objects.");
            IProxyCollection proxyCollection = proxyCollectionFunc();
            var proxyCollectionParameter = new ProxyCollectionParameter(managerComponent.ProxyFactory.ProxyBuilder.ConstructorParameterName, proxyCollection);
            if (bindingContext.Parameters.IsReadOnly)
            {
                var listParameters = new List<IInjectionParameter>(bindingContext.Parameters) { proxyCollectionParameter };
                bindingContext.Parameters = listParameters;
            }
            else
                bindingContext.Parameters.Add(proxyCollectionParameter);

            object activate = _nestedActivator.Activate(constructorInfo, bindingContext);
            for (int index = 0; index < proxyCollection.Count; index++)
            {
                IProxy proxy = proxyCollection[index];
                Action action;
                var interceptorProcess = new List<IInterceptorProcess>(managerComponent.GetInterceptorProcesses(bindingContext.Binding));
                IInterceptor interceptor = managerComponent.CreateInterceptor(proxy, interceptorProcess);
                switch (interceptor.InterceptorType)
                {
                    case InterceptorType.Method:
                        action = () =>
                        {
                            WeakReference weak;
                            if (settings.TryGet(InterceptionManagerComponent.TargetKey, out weak) &&
                                weak != null)
                                interceptor.Target = weak.Target;
                            for (int i = 0; i < interceptorProcess.Count; i++)
                            {
                                IInterceptorProcess process = interceptorProcess[i];
                                process.InterceptMethod((IMethodInterceptor)interceptor);
                            }
                        };
                        break;
                    case InterceptorType.PropertyGet:
                        action = () =>
                        {
                            WeakReference weak;
                            if (settings.TryGet(InterceptionManagerComponent.TargetKey, out weak) &&
                                weak != null)
                                interceptor.Target = weak.Target;
                            for (int i = 0; i < interceptorProcess.Count; i++)
                            {
                                IInterceptorProcess process = interceptorProcess[i];
                                process.InterceptGetProperty((IPropertyGetInterceptor)interceptor);
                            }
                        };
                        break;
                    case InterceptorType.PropertySet:
                        action = () =>
                        {
                            WeakReference weak;
                            if (settings.TryGet(InterceptionManagerComponent.TargetKey, out weak) &&
                                weak != null)
                                interceptor.Target = weak.Target;
                            for (int i = 0; i < interceptorProcess.Count; i++)
                            {
                                IInterceptorProcess process = interceptorProcess[i];
                                process.InterceptSetProperty((IPropertySetInterceptor)interceptor);
                            }
                        };
                        break;
                    case InterceptorType.EventAdd:
                        action = () =>
                        {
                            WeakReference weak;
                            if (settings.TryGet(InterceptionManagerComponent.TargetKey, out weak) &&
                                weak != null)
                                interceptor.Target = weak.Target;
                            for (int i = 0; i < interceptorProcess.Count; i++)
                            {
                                IInterceptorProcess process = interceptorProcess[i];
                                process.InterceptAddEvent((IEventAddInterceptor)interceptor);
                            }
                        };
                        break;
                    case InterceptorType.EventRemove:
                        action = () =>
                        {
                            WeakReference weak;
                            if (settings.TryGet(InterceptionManagerComponent.TargetKey, out weak) &&
                                weak != null)
                                interceptor.Target = weak.Target;
                            for (int i = 0; i < interceptorProcess.Count; i++)
                            {
                                IInterceptorProcess process = interceptorProcess[i];
                                process.InterceptRemoveEvent((IEventRemoveInterceptor)interceptor);
                            }
                        };
                        break;
                    case InterceptorType.Unknown:
                        continue;
                    default:
                        throw Validate.EnumOutOfRange(interceptor.InterceptorType, "interceptorType");
                }
                proxy.OnInvoke = action;
            }
            return activate;
        }

        /// <summary>
        /// Injects values into properties, fields, methods in the specified object.
        /// </summary>
        /// <param name="target">The specified target for inject.</param>
        /// <param name="context">The specified <see cref="IBindingContext"/>.</param>
        public void Activate<T>(ref T target, IBindingContext context)
        {
            _nestedActivator.Activate(ref target, context);
        }

        #endregion
    }
}